﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Input;
using System.Windows.Threading;

using vJine.Core;
using vJine.Lua.GUI.BLL;
using vJine.Lua.GUI.BML;

namespace vJine.Lua.GUI {
    public class NodeConfig : List<NodeConfig>, INotifyPropertyChanged, ICommand {

        public static implicit operator string(NodeConfig nConfig) {
            return nConfig.value as string;
        }

        public static implicit operator double(NodeConfig nConfig) {
            return (double)nConfig.value;
        }

        public NodeConfig Parent {
            get;
            set;
        }

        #region
        public Call<object[], LuaContext, NodeConfig> OnInit;
        public Call<object[], NodeConfig> OnStart;
        public Call<object[], NodeConfig> OnBegin;
        public Call<object[], NodeConfig> OnSet;
        public Call<object[], NodeConfig> OnExec;
        public Call<object[], NodeConfig> OnInvoke;
        public Exec<NodeConfig> OnEnter;
        public Call<object[], NodeConfig> OnFinish;
        public Call<object[], NodeConfig> OnStop;

        public NodeConfig init_config(string name, string alias, Object value) {
            if (this.configs == null) {
                this.configs = new NodeConfig();
            }

            NodeConfig nConfig = new NodeConfig() {
                Parent = this, name = name, alias = alias, value = value
            };
            this.configs.Add(nConfig);

            return nConfig;
        }

        protected NodeConfig get_config(string name) {
            if (this.configs == null || this.configs.Count == 0) {
                return null;
            }

            NodeConfig nConfig = this.configs;
            for (int i = 0; i < nConfig.Count; i++) {
                NodeConfig nConfig_i = nConfig[i];

                if (nConfig.name == name) {
                    return nConfig;
                }
            }

            return null;
        }
        #endregion

        #region ICommand
        public string cmd_run {
            get {
                return this._cmd_run;
            }
            set {
                if (value != this._cmd_run) {
                    this._cmd_run = value;
                    this.NotifyPropertyChanged("cmd_run");
                }
            }
        }
        string _cmd_run = "运行"/*Idle*/; //暂停/*Run*/，重试/*Error*/

        public string cmd_run_param {
            get {
                return this._cmd_run_param;
            }
            set {
                if (value != this._cmd_run_param) {
                    this._cmd_run_param = value;
                    this.NotifyPropertyChanged("cmd_run_param");
                }
            }
        }
        string _cmd_run_param = "run"/*Idle*/; //pause/*Run*/，retry/*Error*/

        bool cmd_run_en = true;
        bool cmd_cancel_en = false;
        bool cmd_ignore_en = false;

        public bool CanExecute(object parameter) {
            string cmd = parameter as string;
            switch (cmd) {
                case "run":
                case "pause":
                case "continue":
                case "retry":
                    return this.is_runnable && this.cmd_run_en;
                case "cancel":
                    return this.is_runnable && this.cmd_cancel_en;
                case "ignore":
                    return false; //this.is_runnable && this.cmd_ignore_en;
                default:
                    return false;
            }
        }

        public event EventHandler CanExecuteChanged;

        internal Dispatcher _guiDispatcer;
        internal Dispatcher guiDispatcer {
            get {
                if (this._guiDispatcer == null) {
                    this._guiDispatcer = Dispatcher.CurrentDispatcher;
                }
                return this._guiDispatcer;
            }
            set {
                this._guiDispatcer = value;
            }
        }
        public void Execute(object parameter) {
            string cmd = parameter as string;
            switch (cmd) {
                case "run":
                    this.switch_status(xStatus.Run);
                    break;
                case "pause":
                    this.switch_status(xStatus.Pause);
                    break;
                case "continue":
                    this.switch_status(xStatus.Continue);
                    break;
                case "retry":
                    this.switch_status(xStatus.Retry);
                    break;
                case "cancel":
                    this.switch_status(xStatus.Cancel);
                    break;
                case "ignore":
                    this.switch_status(xStatus.Ignore);
                    break;
                default:
                    break;
            }
        }

        AutoResetEvent execSignal = new AutoResetEvent(false);

        enum xStatus {
            Idle,
            Run, Pause, Wait, Continue,

            Error, 
            Retry, Ignore, Cancel
        }

        xStatus Status = xStatus.Idle;
        void switch_status(xStatus status, NodeConfig currentNode = null, bool isBefore = true) {
            this.guiDispatcer.Invoke(new Action(() => {
                
                switch (status) {
                    case xStatus.Idle:
                        this.cmd_run_en = true;
                        this.cmd_run_param = "run";
                        this.cmd_run = "运行";

                        this.cmd_cancel_en = false;
                        this.cmd_ignore_en = false;

                        this.Status = xStatus.Idle;
                        break;
                    case xStatus.Run:
                    case xStatus.Continue:
                    case xStatus.Retry:
                    case xStatus.Cancel:
                    case xStatus.Ignore:
                        this.cmd_run_en = true;
                        this.cmd_run_param = "pause";
                        this.cmd_run = "暂停";

                        this.cmd_cancel_en = false;
                        this.cmd_ignore_en = false;

                        if (this.on_continue != null) {
                            this.on_continue();
                        }
                        if (status == xStatus.Run) {
                            if (this.on_init != null) {
                                this.on_init(this);
                            }
                            this.Status = xStatus.Run;
                            this.try_exec(() => {
                                //this.IsActive = true;
                                this.run(this.luaContext);
                            });
                        } else {
                            if (status == xStatus.Continue) {
                                this.Status = xStatus.Run;
                            } else {
                                this.Status = status;
                            }
                            this.execSignal.Set();
                        }
                        break;
                    case xStatus.Pause:
                        this.cmd_run_en = false;
                        this.cmd_cancel_en = false;
                        this.cmd_ignore_en = false;

                        this.Status = xStatus.Pause;
                        if (this.on_pause != null) {
                            this.on_pause(null, isBefore);
                        }
                        break;
                    case xStatus.Wait:
                        this.cmd_run_en = true;
                        this.cmd_run_param = "continue";
                        this.cmd_run = "继续";

                        this.cmd_cancel_en = true;
                        this.cmd_ignore_en = true;

                        this.Status = xStatus.Wait;
                        if (this.on_pause != null) {
                            this.on_pause(currentNode, isBefore);
                        }
                        break;
                    case xStatus.Error:
                        this.cmd_run_en = true;
                        this.cmd_run_param = "retry";
                        this.cmd_run = "重试";

                        this.cmd_cancel_en = true;
                        this.cmd_ignore_en = true;

                        this.Status = xStatus.Error;
                        if (this.on_pause != null) {
                            this.on_pause(null, isBefore);
                        }
                        break;
                }
                if (this.CanExecuteChanged != null) {
                    this.CanExecuteChanged(this, null);
                }
            }));
        }

        void try_exec(Action cmd) {
            this.guiDispatcer.BeginInvoke(new Action(() => {
                if (this.on_begin != null) {
                    this.on_begin(this);
                }

                cmd.BeginInvoke(new AsyncCallback((IAsyncResult ar) => {
                    this.guiDispatcer.BeginInvoke(new Action(() => {
                        if (this.on_finish != null) {
                            this.on_finish(this);
                        }
                    }), null);
                }), null);
            }));
        }

        void NodeConfig_Completed(object sender, EventArgs e) {
            throw new NotImplementedException();
        }

        #endregion ICommand

        public NodeConfig() {
        }

        public NodeConfig(bool is_batch) : this() {
            this.is_batch = is_batch;
        }

        public override string ToString() {
            return string.Format("{0},{1}", this.name, this.uri);
        }

        //public override bool Equals(object obj) {
        //    NodeConfig config = obj as NodeConfig;
        //    if (config == null) {
        //        return false;
        //    }

        //    return this.uri == config.uri;
        //}

        //public override int GetHashCode() {
        //    return base.GetHashCode();
        //}

        /// <summary>
        /// 节点名称（用于程序配置配置）
        /// </summary>
        public string name { get; set; }
        internal string name_path { get; set; }

        /// <summary>
        /// 节点别名（用于用户界面显示）
        /// </summary>
        public string alias {
            get {
                return string.IsNullOrEmpty(this._alias) ? this.name : this._alias;
            }
            set {
                this._alias = value;
            }
        }
        string _alias;
        public string alias_path { get; set; }

        public string path { get; set; }
        
        public string uri { get; set; }
        public string key_config { get; set; }

        string _param = null;
        /// <summary>
        /// 参数名称（命名后在显示在主屏）
        /// </summary>
        public string param {
            get {
                return this._param;
            }
            set {
                if (value != this._param) {
                    this._param = value;
                    this.NotifyPropertyChanged("param");
                }
            }
        }

        /// <summary>
        /// 节点默认值
        /// </summary>
        public Object value {
            get {
                return this._value;
            }
            set {
                if (!Object.Equals(value, this._value)) {
                    this._value = value;
                    this.NotifyPropertyChanged("value");
                }

                if (this.OnSet != null) {
                    this.OnSet(this);
                }
            }
        }
        Object _value;
        internal string value_path { get; set; }

        public string cmd_exec { get; set; }
        public string cmd_w { get; set; }
        public string cmd_r { get; set; }

        public string cmd_set_value{ get; set; }
        public string cmd_changed { get; set; }
        public string cmd_dbclick { get; set; }

        /// <summary>
        /// 禁止节点运行
        /// </summary>
        bool _disable = false;
        public bool disable {
            get {
                return this._disable;
            }
            set {
                if (value != this._disable) {
                    this._disable = value;
                    this.NotifyPropertyChanged("disable");
                }
            }
        }

        /// <summary>
        /// 禁止节点运行
        /// </summary>
        bool _hide = false;
        public bool hide {
            get {
                return this._hide;
            }
            set {
                if (value != this._hide) {
                    this._hide = value;
                    this.NotifyPropertyChanged("hide");
                }
            }
        }

        /// <summary>
        /// 暂停节点运行
        /// </summary>
        bool _pause = false;
        public bool pause {
            get {
                return this._pause;
            }
            set {
                if (value != this._pause) {
                    this._pause = value;
                    this.NotifyPropertyChanged("pause");
                }
            }
        }

        /// <summary>
        /// 显示节点
        /// </summary>
        bool _show_node = false;
        public bool show_node {
            get {
                return this._show_node;
            }
            set {
                if (value != this._show_node) {
                    this._show_node = value;
                    this.NotifyPropertyChanged("show_node");
                }
            }
        }

        /// <summary>
        /// 暂停节点运行后提示信息
        /// </summary>
        string _pause_message = null;
        public string pause_message {
            get {
                return this._pause_message;
            }
            set {
                if (value != this._pause_message) {
                    this._pause_message = value;
                    this.NotifyPropertyChanged("pause_message");
                }
            }
        }

        /// <summary>
        /// 运行节点后暂停运行
        /// </summary>
        bool _pause_after = false;
        public bool pause_after {
            get {
                return this._pause_after;
            }
            set {
                if (value != this._pause_after) {
                    this._pause_after = value;
                    this.NotifyPropertyChanged("pause_after");
                }
            }
        }

        /// <summary>
        /// 显示节点
        /// </summary>
        bool _show_node_after = false;
        public bool show_node_after {
            get {
                return this._show_node_after;
            }
            set {
                if (value != this._show_node_after) {
                    this._show_node_after = value;
                    this.NotifyPropertyChanged("show_node_after");
                }
            }
        }

        /// <summary>
        /// 暂停节点运行后提示信息
        /// </summary>
        string _pause_after_message = null;
        public string pause_after_message {
            get {
                return this._pause_after_message;
            }
            set {
                if (value != this._pause_after_message) {
                    this._pause_after_message = value;
                    this.NotifyPropertyChanged("pause_after_message");
                }
            }
        }

        #region Formula
        /// <summary>
        /// 参数节点表达式（运行节点前计算）
        /// </summary>
        public string formula {
            get {
                return this._formula;
            }
            set {
                if (value != this._formula) {
                    this._formula = value;
                    this.NotifyPropertyChanged("formula");
                }
            }
        }
        string _formula = null;

        /// <summary>
        /// 节点运行前表达式
        /// </summary>
        public string formula_before {
            get {
                return this._formula_before;
            }
            set {
                if (value != this._formula_before) {
                    this._formula_before = value;
                    this.NotifyPropertyChanged("formula_before");
                }
            }
        }
        string _formula_before = null;

        /// <summary>
        /// 节点运行后表达式
        /// </summary>
        public string formula_after {
            get {
                return this._formula_after;
            }
            set {
                if (value != this._formula_after) {
                    this._formula_after = value;
                    this.NotifyPropertyChanged("formula_after");
                }
            }
        }
        string _formula_after = null;
        #endregion Formula

        int _times = 1;
        /// <summary>
        /// 运行次数
        /// </summary>
        public int times {
            get {
                return this._times;
            }
            set {
                if (value != this._times) {
                    this._times = value;
                    this.NotifyPropertyChanged("times");
                }
            }
        }

        int _counter = 0;
        /// <summary>
        /// 运行计数
        /// </summary>
        public int counter {
            get {
                return this._counter;
            }
            set {
                if (value != this._counter) {
                    this._counter = value;
                    this.NotifyPropertyChanged("counter");
                }
            }
        }

        double _delay = 0;
        /// <summary>
        /// 运行延时
        /// </summary>
        public double delay {
            get {
                return this._delay;
            }
            set {
                if (value != this._delay) {
                    this._delay = value;
                    this.NotifyPropertyChanged("delay");
                }
            }
        }

        /// <summary>
        /// 输出日志
        /// </summary>
        public bool log {
            get {
                return this._log;
            }
            set {
                if (value != this._log) {
                    this._log = value;
                    this.NotifyPropertyChanged("log");
                }
            }
        }
        bool _log = true;

        bool _data = false;
        /// <summary>
        /// 保存数据
        /// </summary>
        public bool data {
            get {
                return this._data;
            }
            set {
                if (value != this._data) {
                    this._data = value;
                    this.NotifyPropertyChanged("data");
                }
            }
        }

        public string data_tag {
            get {
                return this._data_tag;
            }
            set {
                if (value != this._data_tag) {
                    this._data_tag = value;
                    this.NotifyPropertyChanged("data_tag");
                }
            }
        }
        string _data_tag;
        internal string data_tag_path;

        public string data_spec {
            get {
                return this._data_spec;
            }
            set {
                if (value != this._data_spec) {
                    this._data_spec = value;
                    this.NotifyPropertyChanged("data_spec");
                }
            }
        }
        string _data_spec;

        public bool param_en {
            get {
                return this._param_en;
            }
            set {
                if (value != this._param_en) {
                    this._param_en = value;
                    this.NotifyPropertyChanged("param_en");
                }
            }
        }
        bool _param_en = true;

        public string param_file {
            get {
                return this._param_file;
            }
            set {
                if (value != this._param_file) {
                    this._param_file = value;
                    this.NotifyPropertyChanged("param_file");
                }
            }
        }
        string _param_file;

        public bool result_en {
            get {
                return this._result_en;
            }
            set {
                if (value != this._result_en) {
                    this._result_en = value;
                    this.NotifyPropertyChanged("result_en");
                }
            }
        }
        bool _result_en = true;

        public string result_file {
            get {
                return this._result_file;
            }
            set {
                if (value != this._result_file) {
                    this._result_file = value;
                    this.NotifyPropertyChanged("result_file");
                }
            }
        }
        string _result_file;

        bool _show_before = false;
        /// <summary>
        /// 运行前提示
        /// </summary>
        public bool show_before {
            get {
                return this._show_before;
            }
            set {
                if (value != this._show_before) {
                    this._show_before = value;
                    this.NotifyPropertyChanged("show_before");
                }
            }
        }

        bool _show_after = false;
        //运行后提示
        public bool show_after {
            get {
                return this._show_after;
            }
            set {
                if (value != this._show_after) {
                    this._show_after = value;
                    this.NotifyPropertyChanged("show_after");
                }
            }
        }

        /// <summary>
        /// 初始节点（程序首次运行强制调用直至成功调用一次）
        /// </summary>
        public bool is_init {
            get {
                return this._is_init;
            }
            set {
                if (value != this._is_init) {
                    this._is_init = value;
                    this.NotifyPropertyChanged("is_init");
                }
            }
        }
        bool _is_init;

        /// <summary>
        /// 终了节点（程序终止前运行）
        /// </summary>
        public bool is_first {
            get {
                return this._is_first;
            }
            set {
                if (value != this._is_first) {
                    this._is_first = value;
                    this.NotifyPropertyChanged("is_first");
                }
            }
        }
        bool _is_first;

        /// <summary>
        /// 终了节点（程序终止前运行）
        /// </summary>
        public bool is_final {
            get {
                return this._is_final;
            }
            set {
                if (value != this._is_final) {
                    this._is_final = value;
                    this.NotifyPropertyChanged("is_final");
                }
            }
        }
        bool _is_final;

        /// <summary>
        /// 测试用例：配置属性后将该节点载入测试用例
        /// </summary>
        public string tc {
            get {
                return this._tc;
            }
            set {
                if (value != this._tc) {
                    this._tc = value;
                    this.NotifyPropertyChanged("tc");
                }
            }
        }
        string _tc;

        /// <summary>
        /// 不调用初始化节点
        /// </summary>
        public bool init_ignore { get; set; }

        /// <summary>
        /// 参数列表
        /// </summary>
        public List<ListItem> list { get; set; }
        public string list_path { get; set; }

        /// <summary>
        /// 多选列表
        /// </summary>
        public bool multi { get; set; }

        /// <summary>
        /// 批量读取
        /// </summary>
        public bool batch_read { get; set; }
        internal string batch_read_path { get; set; }
        /// <summary>
        /// 地址
        /// </summary>
        public string addr { get; set; }
        internal string addr_path { get; set; }
        /// <summary>
        /// bit.最小值
        /// </summary>
        public double bit_min { get; set; }
        internal string bit_min_path { get; set; }
        /// <summary>
        /// bit.最大值
        /// </summary>
        public double bit_max { get; set; }
        internal string bit_max_path { get; set; }

        /// <summary>
        /// value.最小值
        /// </summary>
        public double min { get; set; }
        internal string min_path { get; set; }
        /// <summary>
        /// value.最大值
        /// </summary>
        public double max { get; set; }
        internal string max_path { get; set; }

        /// <summary>
        /// 显示格式
        /// </summary>
        public string format { get; set; }

        #region
        /// <summary>
        /// 是否可写
        /// </summary>
        public bool w {
            get {
                return this._w;
            }
            set {
                if (value != this._w) {
                    this._w = value;
                    this.NotifyPropertyChanged("w");
                }
            }
        }
        bool _w;
        internal string w_path { get; set; }
        /// <summary>
        /// 是否检查写入值
        /// </summary>
        public bool c {
            get {
                return this._c;
            }
            set {
                if (value != this._c) {
                    this._c = value;
                    this.NotifyPropertyChanged("c");
                }
            }
        }
        bool _c;
        internal string c_path { get; set; }
        /// <summary>
        /// 是否可读
        /// </summary>
        public bool r {
            get {
                return this._r;
            }
            set {
                if (value != this._r) {
                    this._r = value;
                    this.NotifyPropertyChanged("r");
                }
            }
        }
        bool _r;
        internal string r_path { get; set; }

        /// <summary>
        /// 节点运行后恢复参数默认值
        /// </summary>
        public bool R {
            get {
                return this._R;
            }
            set {
                if (value != this._R) {
                    this._R = value;
                    this.NotifyPropertyChanged("R");
                }
            }
        }
        bool _R;
        internal string R_path { get; set; }
        /// <summary>
        /// 是否验证读出值
        /// </summary>
        public bool v {
            get {
                return this._v;
            }
            set {
                if (value != this._v) {
                    this._v = value;
                    this.NotifyPropertyChanged("v");
                }
            }
        }
        bool _v;
        internal string v_path { get; set; }

        /// <summary>
        /// 是否是设置项
        /// </summary>
        public bool p {
            get {
                return this._p;
            }
            set {
                if (value != this._p) {
                    this._p = value;
                    this.NotifyPropertyChanged("p");
                }
            }
        }
        bool _p;
        internal string p_path { get; set; }

        /// <summary>
        /// 是否保存节点更改值
        /// </summary>
        public bool s {
            get {
                return this._s;
            }
            set {
                if (value != this._s) {
                    this._s = value;
                    this.NotifyPropertyChanged("s");
                }
            }
        }
        bool _s;
        internal string s_path { get; set; }
        #endregion

        /// <summary>
        /// 备注
        /// </summary>
        public string remark {
            get {
                return this._remark;
            }
            set {
                if (value != this._remark) {
                    this._remark = value;
                    this.NotifyPropertyChanged("remark");
                }
            }
        }
        string _remark;

        public string gui {
            get {
                return this._gui;
            }
            set {
                if (value != this._gui) {
                    this._gui = value;
                    this.NotifyPropertyChanged("gui");
                }
            }
        }
        string _gui;

        /// <summary>
        /// 节点参数
        /// </summary>
        public NodeConfig configs { get; set; }

        public LuaContext luaContext { get; set; }
        string lua_root = null;
        public void set(LuaContext context, string root, bool  set_init = true, bool set_config = false) {
            this.luaContext = context;
            this.lua_root = root;

            this.path = root;

            if (string.IsNullOrEmpty(this.uri)) {
                this.uri = this.name;
            } else {
                this.uri = this.uri + "." + this.name;
            }
            if (this.param == "*") {
                this.param = this.name;
            }
            this.name_path = root + ".name";
            this.addr_path = root + ".addr";
            this.batch_read_path = root + ".batch_read";
            this.bit_min_path = root + ".bit_min";
            this.bit_max_path = root + ".bit_max";
            this.min_path = root + ".min";
            this.max_path = root + ".max";

            this.value_path = root + ".value";
            this.list_path = root + ".list";
            this.tab_path = root + ".tab";

            this.w_path = root + ".w";
            this.c_path = root + ".c";
            this.r_path = root + ".r";
            this.v_path = root + ".v";

            this.p_path = root + ".p";
            this.s_path = root + ".s";

            this.data_tag_path = root + ".data_tag";

            if (!this.w && !this.r && !this.data && !this.p) {
                this.w = true;
            }

            if (set_config) {
                context.set(this.tab_path, this.tab);

                context.set(this.value_path, this.value);
                context.set(this.list_path, this.list);

                context.set(this.w_path, this.w);
                context.set(this.c_path, this.c);
                context.set(this.r_path, this.r);
                context.set(this.v_path, this.v);

                context.set(this.name_path, this.name);
                context.set(this.addr_path, this.addr);
                context.set(this.batch_read_path, this.batch_read);
                context.set(this.bit_min_path, this.bit_min);
                context.set(this.bit_max_path, this.bit_max);
                context.set(this.min_path, this.min);
                context.set(this.max_path, this.max);
            }

            if (string.IsNullOrEmpty(this.cmd_exec)) {
                this.cmd_exec = root + ".cmd";
            }
            if (!context.is_function(this.cmd_exec)) {
                this.cmd_exec = null;
            }
            if (string.IsNullOrEmpty(this.cmd_w)) {
                this.cmd_w = root + ".cmd_w";
            }
            if (!context.is_function(this.cmd_w)) {
                this.cmd_w = null;
            }
            if (string.IsNullOrEmpty(this.cmd_r)) {
                this.cmd_r = root + ".cmd_r";
            }
            if (!context.is_function(this.cmd_r)) {
                this.cmd_r = null;
            }

            if (string.IsNullOrEmpty(this.cmd_dbclick)) {
                this.cmd_dbclick = root + ".on_dbclick";
            }
            if (!context.is_function(this.cmd_dbclick)) {
                this.cmd_dbclick = null;
            }

            this.set_value(context, this);
            if (string.IsNullOrEmpty(this.cmd_changed)) {
                this.cmd_changed = root + ".on_changed";
            }
            if (!context.is_function(this.cmd_changed)) {
                this.cmd_changed = null;
            }

            this.cmd_set_value = root + ".set_value";

            //this.bind_changed(context, this);

            List<NodeConfig> inits_temp = this.inits ?? new List<NodeConfig>();
            for (int i = 0, len = this.Count; i < len; i++) {
                NodeConfig nConfig_i = this[i];
                nConfig_i.uri = this.uri;
                nConfig_i.Parent = this;

                if (set_init) {
                    nConfig_i.inits = inits_temp;
                    if (nConfig_i.is_init && !inits_temp.Contains(nConfig_i)) {
                        inits_temp.Add(nConfig_i);
                    }
                }
                nConfig_i.set(context, string.Format("{0}.{1}", root, i + 1), set_config: set_config);

                this.bind_changed(context, nConfig_i);
            }
            this.inits = inits_temp;

            if (context.is_object(root)) {
                context.get(root);
                context.get("__NODE_INDEX_HELPER");
                context.nexec("setmetatable", 2);
            }

            if (this.configs != null && this.configs.Count > 0) {
                for (int i = 0, len = this.configs.Count; i < len; i++) {
                    NodeConfig config_i = this.configs[i];
                    config_i.uri = this.uri;
                    config_i.Parent = this;

                    config_i.inits = this.inits;
                    config_i.set(context, string.Format("{0}.configs.{1}", root, i + 1), set_config: set_config);

                    this.bind_changed(context, config_i);
                }

                //TODO:设置上下文报错
                context.get(root + ".configs");
                context.get("__CONFIG_INDEX_HELPER");
                context.nexec("setmetatable", 2);
            }
        }

        void bind_changed(LuaContext context, NodeConfig nConfig) {
            nConfig.PropertyChanged += (object sender, PropertyChangedEventArgs e) => {
                switch (e.PropertyName) {
                    case "value":
                        nConfig.set_value(context, nConfig);

                        if (!nConfig.trig_on_changed) {
                            return;
                        }

                        //保存配置值
                        if (this.on_save != null) {
                            this.on_save(context, nConfig);
                        }
                        //
                        if (context.is_function(nConfig.cmd_changed)) {
                            this.try_exec(() => {
                                try {
                                    this.exec(context, nConfig, nConfig.cmd_changed);
                                } finally {
                                    this.switch_status(xStatus.Idle);
                                }
                            });
                        }
                        break;
                    case "tab":
                        context.set(nConfig.tab_path, nConfig.tab);
                        break;
                    case "w":
                        context.set(nConfig.w_path, nConfig.w);
                        break;
                    case "c":
                        context.set(nConfig.c_path, nConfig.c);
                        break;
                    case "r":
                        context.set(nConfig.r_path, nConfig.r);
                        break;
                    case "v":
                        context.set(nConfig.v_path, nConfig.v);
                        break;
                    case "s":
                        context.set(nConfig.s_path, nConfig.s);
                        if (nConfig.s) {
                            nConfig.NotifyPropertyChanged("value");
                        }
                        break;
                    case "p":
                        context.set(nConfig.p_path, nConfig.p);
                        if (nConfig.p) {
                            nConfig.s = true;
                        }
                        break;
                    case "data_tag":
                        context.set(nConfig.data_tag_path, nConfig.data_tag);
                        break;
                    default:
                        break;
                }
            };
        }

        public NodeConfig clone(NodeConfig parent = null) {
            //
            NodeConfig nConfig = new NodeConfig() {
                Parent = parent ?? this.Parent,

                name = this.name, alias = this.alias, uri = this.uri, path = this.path,
                luaContext = this.luaContext,
                //
                s = this.s,
                value = this.value, list = this.list, multi = this.multi,
                value_path = this.value_path,
                //
                tab = this.tab, tab_path = this.tab_path,
                w = this.w, c = this.c, r = this.r, v = this.v, p = this.p, is_editable = this.is_editable,
                w_path = this.w_path, c_path = this.c_path,
                r_path = this.r_path, v_path = this.v_path, p_path = this.p_path, s_path = this.s_path,
                data = this.data,
                pause = this.pause, pause_message = this.pause_message,
                //
                param = this.param, formula = this.formula,
                //cmds
                cmd_exec = this.cmd_exec,
                cmd_dbclick = this.cmd_dbclick, cmd_changed = this.cmd_changed,
                cmd_set_value = this.cmd_set_value,

                inits = this.inits,

                OnInit = this.OnInit,
                OnStart = this.OnStart,
                OnBegin = this.OnBegin,
                OnSet = this.OnSet,
                OnExec = this.OnExec,
                OnInvoke = this.OnInvoke,
                OnEnter = this.OnEnter,
                OnFinish = this.OnFinish,
                OnStop = this.OnStop
            };
            
            //value-list
            if (this.list != null) {
                nConfig.list = new List<ListItem>();
                for (int i = 0, len = this.list.Count; i < len; i++) {
                    ListItem list_i = this.list[i];
                    nConfig.list.Add(
                        new ListItem() {
                            Checked = list_i.Checked, Value = list_i.Value
                        });
                }
            }
            //.configs
            if (this.configs != null && this.configs.Count > 0) {
                nConfig.configs = new NodeConfig() { Parent = nConfig };
                for (int i = 0, len = this.configs.Count; i < len; i++) {
                    nConfig.configs.Add(this.configs[i].clone(nConfig));
                }
            }
            //binding
            nConfig.bind_changed(this.luaContext, nConfig);

            //Siblings
            for (int i = 0, len = this.Count; i < len; i++) {
                NodeConfig nconfig_i = this[i].clone();
                nconfig_i.Parent = nConfig.Parent;
                nConfig.Add(nconfig_i);
            }
            
            return nConfig;
        }

        /// <summary>
        /// 设置NodeConfig.value的值
        /// </summary>
        /// <param name="luaContext">上下文</param>
        /// <param name="nConfig">节点</param>
        void set_value(LuaContext luaContext, NodeConfig nConfig) {
            if (nConfig.list == null) {
                luaContext.set(nConfig.value_path, nConfig.value);
            } else if (nConfig.multi) {
                List<Object> objs = new List<object>();
                for (int i = 0, len = nConfig.list.Count; i < len; i++) {
                    ListItem list_i = nConfig.list[i];
                    if (list_i.Checked) {
                        objs.Add(list_i.Value);
                    }
                }
                luaContext.set(nConfig.value_path, objs);
                nConfig.NotifyPropertyChanged("list");
            } else {
                for (int i = 0, len = nConfig.list.Count; i < len; i++) {
                    ListItem list_i = nConfig.list[i];
                    if (list_i.Checked) {
                        luaContext.set(nConfig.value_path, list_i.Value);
                        break;
                    }
                }
                nConfig.NotifyPropertyChanged("list");
            }
        }

        void show_log(LuaContext context, string uri, string msg, params object[] args) {
            this.show_message(context, "on_log", msg, args);
        }

        void show_warn(LuaContext context, string uri, string msg, params object[] args) {
            this.show_message(context, "on_warn", msg, args);
        }

        /// <summary>
        /// 重试:true,忽略:false,取消:null
        /// </summary>
        /// <param name="context"></param>
        /// <param name="uri"></param>
        /// <param name="msg"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        void show_error(LuaContext context, string uri, string msg, params object[] args) {
            this.show_message(context, "on_error", msg, args);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="cmd"></param>
        /// <param name="msg"></param>
        /// <param name="args"></param>
        /// <returns>True:Retry, False:Ignore, null:Cancel</returns>
        void show_message(LuaContext context, string cmd, string msg, params object[] args) {
            if (context.is_function(cmd)) {
                context.exec(cmd, string.Format(msg, args));
            }
        }

        /// <summary>
        /// 初始化节点集合
        /// </summary>
        internal List<NodeConfig> inits;
        /// <summary>
        /// 标记初始化节点是否已成功调用
        /// </summary>
        bool isIntialized = false;
        public void init(LuaContext context, bool force = false) {
            this.init(context, this, force);
        }
        public void init(LuaContext context, NodeConfig luaConfig, bool force = false) {
            if (luaConfig.inits == null || luaConfig.inits.Count == 0) {
                return;
            }

            for (int i = 0, len = luaConfig.inits.Count; i < len; i++) {
                NodeConfig config_init = luaConfig.inits[i];
                if (!config_init.is_init) {
                    continue;
                } else if (config_init.isIntialized && !force) {
                    continue;
                } else if (config_init == luaConfig) {
                    continue;
                }

                this.exec(context, config_init, config_init.cmd_exec);
                
                if (this.Status == xStatus.Cancel) {
                    return;
                }
                config_init.isIntialized = true;
            }
        }

        object exec(LuaContext context, NodeConfig runConfig, string cmd, bool show_log = true) {
            Call<object[], NodeConfig> onInvoke = null;
            if (!context.is_function(cmd)) {
                if (runConfig.OnInvoke != null) {
                    onInvoke = runConfig.OnInvoke;
                } else {
                    this.show_error(context, runConfig.uri, "<节点未调用.命令未找到>,{0}:{1}", runConfig.uri, cmd);
                    return null;
                }
            }
            if (runConfig.disable) {
                this.show_warn(context, runConfig.uri, "<节点未调用.禁止调用>,{0}", runConfig.uri);
                return null;
            }

            if (runConfig.is_init && runConfig.isIntialized) {
                return null;
            }

            this.init(context, runConfig);
            if (this.Status == xStatus.Cancel) {
                this.show_warn(context, runConfig.uri, "<节点未调用.取消调用>,{0}", runConfig.uri);
                return null;
            }

            bool is_cmd_exec = cmd == runConfig.cmd_exec; 

            //设置参数
            NodeConfig configs = runConfig.configs;
            runConfig.NotifyPropertyChanged("tab");
            if (configs != null && configs.Count > 0) {
                for (int k = 0, len_k = configs.Count; k < len_k; k++) {
                    NodeConfig config_k = configs[k];
                    context.reg(config_k.cmd_set_value, new Exec<Object>((Object v) => {
                        if (config_k.list == null) {
                            config_k.value = v;
                        } else {
                            foreach (ListItem li in config_k.list) {
                                li.Checked = Object.Equals(li.Value, v);
                            }
                        }
                        
                    }));

                    bool config_trig_on_changed = config_k.trig_on_changed;
                    config_k.trig_on_changed = false;
                    try {
                        config_k.NotifyPropertyChanged("value");
                    } finally {
                        config_k.trig_on_changed = config_trig_on_changed;
                    }
                }
            }

        RETRY: ;
            try {
                if (runConfig.log && show_log) {
                    this.show_log(context, runConfig.uri, "[调用] " + runConfig.uri);
                }

                if (onInvoke != null) {
                    object[] objs = onInvoke(runConfig);
                    this.delay_second(runConfig.delay);

                    if (objs != null && objs.Length > 0) {
                        return objs[0];
                    } else {
                        return null;
                    }
                } else {
                    string root_base = runConfig.path;
                    if (!is_cmd_exec) {
                        root_base = root_base.Substring(0, root_base.LastIndexOf("."));
                        root_base = root_base.Substring(0, root_base.LastIndexOf("."));
                    }

                    context.chain(root_base); //set _G.this,_G.super
                    //(context, value)
                    root_base = root_base.Substring(0, root_base.LastIndexOf("."));
                    context.get(root_base); //
                    object[] objs = context.nexec(cmd, 1, runConfig.value);
                    context.eval("_G['--PARAMS--'] = nil"); //清除全局参数
                    this.delay_second(runConfig.delay);

                    if (objs != null && objs.Length > 0) {
                        return objs[0];
                    } else {
                        return null;
                    }
                }
            } catch (Exception ex) {
                if (ex.InnerException != null) {
                    ex = ex.InnerException;
                }
                //if (!this.IsActive) {
                //    throw new CoreException("节点:<{0}> 调用失败, {1}", runConfig.uri, ex.Message);
                //}
                this.show_error(context,
                    runConfig.uri, "节点:<{0}> 调用失败, {1}", runConfig.uri, ex.Message);
                this.switch_status(xStatus.Error);
                this.execSignal.WaitOne();
                if (this.Status == xStatus.Retry) {
                    this.Status = xStatus.Run;
                    goto RETRY;
                } else if (this.Status == xStatus.Ignore) {
                    this.Status = xStatus.Run;
                    this.show_warn(context, runConfig.uri, "节点:<{0}>, 调用被忽略 ", runConfig.uri);
                } else {
                    runConfig.counter = 0;
                    this.show_warn(context, runConfig.uri, "节点:<{0}>, 调用被取消 ", runConfig.uri);
                }
                return null;
            }
        }

        void delay_second(double second) {
            if (second <= 0) {
                return;
            }

            Thread.Sleep((int)(second * 1000));
        }

        public object exec(LuaContext context, string cmd, bool show_log = true) {
            return this.exec(context, this, cmd, show_log);
        }

        public bool is_runnable {
            get {
                if (this.ui_ignore) {
                    return false;
                } else if (this.counter < this.times) {
                    return true;
                } else if (this.is_batch && this.Count > 0) {
                    return true;
                } else if (this.OnInvoke != null) {
                    return true;
                } else if (this.cmd_exec != null) {
                    return true;
                } 

                return false;
            }
        }

        public event Action<double, double> OnProgress;

        /// <summary>
        /// 运行节点下的多个节点(如Main)
        /// </summary>
        public bool is_batch { get; set; }
        public void run(LuaContext context){
            FormulaEvaluator feCtx = new FormulaEvaluator();
            string err_msg = null;
            StringBuilder sbCheckResult = new StringBuilder();
            this.current_spec = null;
            try {
                if (context.is_function("on_clear")) {
                    context.exec("on_clear");
                }

                this.OnProgress(0, 0);
                this.show_log(this.luaContext, "", "[运行开始]");

                int param_data_count =
                    feCtx.Compile(
                        context, this, 
                        this.param_en ? this.param_file : null
                        );
                param_data_count = param_data_count == 0 ? 1 : param_data_count;
                int param_data_index = 1;

                bool is_first_run = true;
                int counter_orig = this.counter;
                do {
                    this.counter = counter_orig;
                    while (this.counter < this.times) {
                        this.counter += 1;

                        feCtx.eval_before(context);

                        if (this.is_batch) {
                            for (int i = 0, len = this.Count; i < len; i++) {
                                NodeConfig runConfig_i = this[i];
                                runConfig_i.Parent = this;

                                runConfig_i.counter = 0;
                                try {
                                    if (runConfig_i.times == 1) {
                                        feCtx.eval_params(context, runConfig_i, param_data_index);
                                    }

                                    for (; runConfig_i.counter < runConfig_i.times;) {
                                        runConfig_i.counter++;
                                        if (runConfig_i.times > 1) {
                                            feCtx.eval_params(context, runConfig_i, param_data_index, runConfig_i);
                                        }

                                        if (this.OnProgress != null) {
                                            this.OnProgress(
                                                100.0 * ((param_data_index - 1) * this.times + this.counter) / (this.times * param_data_count),
                                                100.0 * (i + 1) / len);
                                        }

                                        if (runConfig_i.disable) {
                                            continue;
                                        } else if (runConfig_i.is_final) {
                                            continue;
                                        } else if (!is_first_run && runConfig_i.is_first) {
                                            continue;
                                        }
                                        is_first_run = false;

                                        if (runConfig_i.pause || this.Status == xStatus.Pause) {
                                            this.show_warn(context, runConfig_i.uri,
                                                "[暂停运行] " + runConfig_i.uri + (string.IsNullOrEmpty(runConfig_i.pause_message) ? "" : "," + runConfig_i.pause_message));
                                            this.switch_status(xStatus.Wait, runConfig_i.show_node?runConfig_i:null, true);
                                            this.execSignal.WaitOne();
                                            if (this.Status == xStatus.Ignore) {
                                                this.show_warn(context, runConfig_i.uri, "[忽略运行] " + runConfig_i.uri);
                                                continue;
                                            } else if (this.Status == xStatus.Cancel) {
                                                this.show_warn(context, runConfig_i.uri, "[取消运行] " + runConfig_i.uri);
                                                return;
                                            } //else continue
                                            else {
                                                this.show_warn(context, runConfig_i.uri, "[继续运行] " + runConfig_i.uri);
                                            }
                                        }

                                        this.exec(context, runConfig_i, runConfig_i.cmd_exec, this.log);

                                        if (this.Status == xStatus.Cancel) {
                                            return;
                                        }

                                        if (runConfig_i.pause_after) {
                                            this.show_warn(context, runConfig_i.uri,
                                                "[暂停运行] " + runConfig_i.uri + (string.IsNullOrEmpty(runConfig_i.pause_after_message) ? "" : "," + runConfig_i.pause_after_message));
                                            this.switch_status(xStatus.Wait, runConfig_i.show_node_after?runConfig_i:null, false);
                                            this.execSignal.WaitOne();
                                            if (this.Status == xStatus.Ignore) {
                                                this.show_warn(context, runConfig_i.uri, "[忽略运行] " + runConfig_i.uri);
                                                continue;
                                            } else if (this.Status == xStatus.Cancel) {
                                                this.show_warn(context, runConfig_i.uri, "[取消运行] " + runConfig_i.uri);
                                                return;
                                            } //else continue
                                            else {
                                                this.show_warn(context, runConfig_i.uri, "[继续运行] " + runConfig_i.uri);
                                            }
                                        }
                                    }
                                } finally {
                                    runConfig_i.counter = 0;
                                }
                            }
                        } else {
                            feCtx.eval_params(context, this, param_data_index);
                            if (this.is_init) {
                                this.isIntialized = false;
                            }
                            this.init(context, this);
                            if (this.Status == xStatus.Cancel) {
                                return;
                            }

                            if (this.OnProgress != null) {
                                this.OnProgress(
                                    100.0 * ((param_data_index - 1) * this.times + this.counter) / (this.times * param_data_count),
                                    100.0);
                            }

                            if (this.pause || this.Status == xStatus.Pause) {
                                this.show_warn(context, this.uri, "[暂停运行] " + this.uri);
                                this.switch_status(xStatus.Wait);
                                this.execSignal.WaitOne();
                                if (this.Status == xStatus.Ignore) {
                                    this.show_warn(context, this.uri, "[忽略运行] " + this.uri);
                                    continue;
                                } else if (this.Status == xStatus.Cancel) {
                                    this.show_warn(context, this.uri, "[取消运行] " + this.uri);
                                    return;
                                } //else continue
                                else {
                                    this.show_warn(context, this.uri, "[继续运行] " + this.uri);
                                }
                            }

                            this.exec(context, this, this.cmd_exec, this.log);
                            
                            if (this.Status == xStatus.Cancel) {
                                return;
                            }
                        }

                        feCtx.eval_after(context);

                        if (this.data) {
                            string spec_result = this.on_data(this.uri, this);
                            if (!string.IsNullOrEmpty(spec_result)) {
                                sbCheckResult.AppendLine(spec_result);
                            }
                        }

                        this.delay_second(this.delay);
                    }
                } while (param_data_index++ < param_data_count);
            } catch (Exception ex) {
                ex = ex.InnerException ?? ex;
                err_msg = ex.Message;

            } finally {
                feCtx.set_defaults();

                if (this.is_batch && this.counter > 0) {
                    for (int i = 0, len = this.Count; i < len; i++) {
                        try {
                            NodeConfig finalConfig = this[i];
                            if (!finalConfig.is_final) {
                                continue;
                            }

                            this.exec(context, finalConfig, finalConfig.cmd_exec);
                        } catch {
                        }
                    }
                }

                this.counter = 0;
                {//判断状态
                    if (!string.IsNullOrEmpty(err_msg)) {
                        this.show_error(this.luaContext, "", "[运行失败]" + err_msg);
                    } else if (this.Status == xStatus.Cancel) {
                        this.show_warn(this.luaContext, "", "[运行取消]");
                    } else {
                        this.show_log(this.luaContext, "", "[运行结束]");
                    }
                    if (sbCheckResult.Length > 0) {
                        this.show_warn(this.luaContext, "main", "===规格验证失败===\r\n" + sbCheckResult.ToString());
                    }
                    this.switch_status(xStatus.Idle);
                }

                try {
                    this.luaContext.gc();
                } catch (Exception ex) {
                    if (ex.InnerException != null) {
                        ex = ex.InnerException;
                    }

                    this.show_error(this.luaContext, "", "[回收资源],错误: " + ex.Message);
                }
            }
        }

        public NodeConfig get(string uri) {
            //Global
            if (this.key_config == uri || this.uri == uri) {
                return this;
            }
            //Local
            if(uri.IndexOf('.') < 0 && this.uri.EndsWith("." + uri)){
                return this;
            }

            if (this.configs != null) {
                for (int i = 0, len = this.configs.Count; i < len; i++) {
                    NodeConfig config_i = this.configs[i].get(uri);
                    if (config_i != null) {
                        return config_i;
                    }
                }
            }

            for (int i = 0, len = this.Count; i < len; i++) {
                NodeConfig config_i = this[i].get(uri);
                if (config_i != null) {
                    return config_i;
                }
            }

            return null;
        }

        #region UI
        /// <summary>
        /// 界面切换时默认tab索引(0:输入参数, 1:输出结果, 2:节点选项)
        /// </summary>
        public int tab {
            get {
                return this._tab;
            }
            set {
                if (value != this._tab) {
                    this._tab = value;
                    this.NotifyPropertyChanged("tab");
                }
            }
        }
        int _tab = 1;
        internal string tab_path { get; set; }

        /// <summary>
        /// UI节点不可拖动，TODO:进一步统一
        /// </summary>
        public bool ui_ignore {
            get {
                return this._ui_ignore;
            }
            set {
                if (value != this._ui_ignore) {
                    this._ui_ignore = value;
                    this.NotifyPropertyChanged("ui_ignore");
                }
            }
        }
        bool _ui_ignore;

        /// <summary>
        /// 强制在[输出]页可编辑
        /// </summary>
        public bool is_editable {
            get {
                return this._is_editable;
            }
            set {
                if (value != this._is_editable) {
                    this._is_editable = value;
                    this.NotifyPropertyChanged("is_editable");
                }
            }
        }
        bool _is_editable;

        public bool IsActive {
            get { return this._IsActive; }
            set {
                if (value != this._IsActive) {
                    this._IsActive = value;
                    this.NotifyPropertyChanged("IsActive");
                }
            }
        }
        private bool _IsActive;

        private bool _IsExpanded;
        public bool IsExpanded {
            get { return this._IsExpanded; }
            set {
                if (value != this._IsExpanded) {
                    this._IsExpanded = value;
                    this.NotifyPropertyChanged("IsExpanded");
                }
            }
        }
        #endregion

        #region INotifyPropertyChanged 成员
        bool _trig_on_changed = true;
        internal bool trig_on_changed {
            get {
                return this._trig_on_changed;
            }
            set {
                if (this.configs != null && this.configs.Count > 0) {
                    for (int i = 0, len = this.configs.Count; i < len; i++) {
                        NodeConfig nConfig_i = this.configs[i];
                        if (nConfig_i != null) {
                            nConfig_i.trig_on_changed = value;
                        }
                    }
                }
                this._trig_on_changed = value;
            }
        }

        internal void NotifyPropertyChanged(string Name) {
            if (this.PropertyChanged != null) {
                try {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(Name));
                } catch (Exception ex) {
                    if (ex == vJine.Core.CoreException.Cancel) {
                        return;
                    }
                    throw ex;
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Helpers
        string current_spec = null;
        ProdSpec prodSpec = null;
        string on_data(string uri, NodeConfig data) {
            if (data == null || data.configs == null || data.configs.Count == 0) {
                throw new CoreException("[错误] 节点[{0}]无数据", uri);
            }

            if (this.current_spec != this.data_spec) {
                if (string.IsNullOrEmpty(this.data_spec)) {
                    this.prodSpec = null;
                } else if (!File.Exists(this.data_spec)) {
                    throw new CoreException("指定的规格文件不存在:[{0}]", this.data_spec);
                } else {
                    if (this.prodSpec == null) {
                        this.prodSpec = new ProdSpec();
                    }
                    this.prodSpec.load(this.data_spec);
                }
            }

            string check_result = null;
            if (this.prodSpec != null) {
                check_result = this.prodSpec.check(data);
            }

            string data_file = "./data/test_data.csv";
            if (this.result_en && !string.IsNullOrEmpty(this.result_file)) {
                string data_dir = Path.GetDirectoryName(this.result_file);
                Directory.CreateDirectory(data_dir);
                data_file = this.result_file;
            }

            //生成文件头
            StringBuilder sbContents = new StringBuilder();
            sbContents.Append("data-tag,");
            for (int i = 0, len_max = data.configs.Count - 1; i <= len_max; i++) {
                NodeConfig data_i = data.configs[i];
                if (!data_i.data || string.IsNullOrEmpty(data_i.param)) {
                    continue;
                }

                sbContents.Append(data_i.param).Append(i < len_max ? "," : "");
            }

            if (!File.Exists(data_file)) {
                sbContents.AppendLine();
            } else {
                IEnumerable<string> lines = File.ReadLines(data_file, Encoding.Default);
                foreach(string line in lines) {
                    if (line == sbContents.ToString()) {
                        break;
                    } else {
                        throw new CoreException("数据列名不一致，请确认后重试");
                    }
                }

                sbContents.Clear();
            }

            string data_tag = string.IsNullOrEmpty(data.data_tag) ? "" : data.data_tag;
            sbContents.Append(data_tag).Append(",");
            for (int i = 0, len_max = data.configs.Count - 1; i <= len_max; i++) {
                NodeConfig data_i = data.configs[i];
                if (!data_i.data || string.IsNullOrEmpty(data_i.param)) {
                    continue;
                }

                string v = data_i.value == null ? "" : data_i.value.ToString();
                sbContents.Append(v).Append(i < len_max ? "," : "");
            }
            sbContents.AppendLine();

            Directory.CreateDirectory(Path.GetDirectoryName(data_file));
            File.AppendAllText(data_file, sbContents.ToString(), Encoding.Default);

            if (!string.IsNullOrEmpty(check_result)) {
                this.show_warn(this.luaContext, "main", "规格验证失败:" + check_result);
            }
            return check_result;
        }
        #endregion

        #region Events
        public event Action<NodeConfig> on_init;
        public event Action<NodeConfig> on_begin;
        public event Action<NodeConfig, bool> on_pause;
        public event Action on_continue;
        public event Action<string> on_log;
        public event Action<string> on_warn;
        public event Action<string> on_error;
        public event Action<NodeConfig> on_finish;

        public Exec<LuaContext, NodeConfig> on_save;
        #endregion Events
    }
}
